<template>
  <div :class="getClass" :style="getWrapperStyle">
    <img
      v-show="isReady"
      ref="imgElRef"
      :src="src"
      :alt="alt"
      :crossorigin="crossorigin"
      :style="getImageStyle"
    />
  </div>
</template>
<script lang="ts">
import type { CSSProperties } from 'vue'
import {
  defineComponent,
  onMounted,
  ref,
  unref,
  computed,
  onUnmounted,
} from 'vue'
import Cropper from 'cropperjs'
import 'cropperjs/dist/cropper.css'
import { useDesign } from '@/hooks/web/useDesign'
import { useDebounceFn } from '@vueuse/shared'

type Options = Cropper.Options

const defaultOptions: Options = {
  aspectRatio: 1,
  zoomable: true,
  zoomOnTouch: true,
  zoomOnWheel: true,
  cropBoxMovable: true,
  cropBoxResizable: true,
  toggleDragModeOnDblclick: true,
  autoCrop: true,
  background: true,
  highlight: true,
  center: true,
  responsive: true,
  restore: true,
  checkCrossOrigin: true,
  checkOrientation: true,
  scalable: true,
  modal: true,
  guides: true,
  movable: true,
  rotatable: true,
}

const props = {
  src: { type: String, required: true },
  alt: { type: String },
  circled: { type: Boolean, default: false },
  realTimePreview: { type: Boolean, default: true },
  height: { type: [String, Number], default: '360px' },
  crossorigin: {
    type: String as PropType<'' | 'anonymous' | 'use-credentials' | undefined>,
    default: undefined,
  },
  imageStyle: {
    type: Object as PropType<CSSProperties>,
    default: () => ({}),
  },
  options: { type: Object as PropType<Options>, default: () => ({}) },
}

export default defineComponent({
  name: 'CropperImage',
  props,
  emits: ['cropend', 'ready', 'cropendError'],
  setup(props, { attrs, emit }) {
    const imgElRef = ref<ElRef<HTMLImageElement>>()
    const cropper = ref<Nullable<Cropper>>()
    const isReady = ref(false)

    const { prefixCls } = useDesign('cropper-image')
    const debounceRealTimeCroppered = useDebounceFn(realTimeCroppered, 80)

    const getImageStyle = computed((): CSSProperties => {
      return {
        height: props.height,
        maxWidth: '100%',
        ...props.imageStyle,
      }
    })

    const getClass = computed(() => {
      return [
        prefixCls,
        attrs.class,
        {
          [`${prefixCls}--circled`]: props.circled,
        },
      ]
    })

    const getWrapperStyle = computed((): CSSProperties => {
      return { height: `${props.height}`.replace(/px/, '') + 'px' }
    })

    onMounted(init)

    onUnmounted(() => {
      cropper.value?.destroy()
    })

    async function init() {
      const imgEl = unref(imgElRef)
      if (!imgEl) {
        return
      }
      cropper.value = new Cropper(imgEl, {
        ...defaultOptions,
        ready: () => {
          isReady.value = true
          realTimeCroppered()
          emit('ready', cropper.value)
        },
        crop() {
          debounceRealTimeCroppered()
        },
        zoom() {
          debounceRealTimeCroppered()
        },
        cropmove() {
          debounceRealTimeCroppered()
        },
        ...props.options,
      })
    }

    // Real-time display preview
    function realTimeCroppered() {
      props.realTimePreview && croppered()
    }

    // event: return base64 and width and height information after cropping
    function croppered() {
      if (!cropper.value) {
        return
      }
      let imgInfo = cropper.value.getData()
      const canvas = props.circled
        ? getRoundedCanvas()
        : cropper.value.getCroppedCanvas()
      canvas.toBlob((blob) => {
        if (!blob) {
          return
        }
        let fileReader: FileReader = new FileReader()
        fileReader.readAsDataURL(blob)
        fileReader.onloadend = (e) => {
          emit('cropend', {
            imgBase64: e.target?.result ?? '',
            imgInfo,
          })
        }
        fileReader.onerror = () => {
          emit('cropendError')
        }
      }, 'image/png')
    }

    // Get a circular picture canvas
    function getRoundedCanvas() {
      const sourceCanvas = cropper.value!.getCroppedCanvas()
      const canvas = document.createElement('canvas')
      const context = canvas.getContext('2d')!
      const width = sourceCanvas.width
      const height = sourceCanvas.height
      canvas.width = width
      canvas.height = height
      context.imageSmoothingEnabled = true
      context.drawImage(sourceCanvas, 0, 0, width, height)
      context.globalCompositeOperation = 'destination-in'
      context.beginPath()
      context.arc(
        width / 2,
        height / 2,
        Math.min(width, height) / 2,
        0,
        2 * Math.PI,
        true,
      )
      context.fill()
      return canvas
    }

    return {
      getClass,
      imgElRef,
      getWrapperStyle,
      getImageStyle,
      isReady,
      croppered,
    }
  },
})
</script>
<style lang="less">
@prefix-cls: ~'@{namespace}-cropper-image';

.@{prefix-cls} {
  &--circled {
    .cropper-view-box,
    .cropper-face {
      border-radius: 50%;
    }
  }
}
</style>
